package com.ruoyi.common.security.aspect;

import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.security.annotation.Idempotent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * @
 */
@Aspect
@Component
@Slf4j
public class IdempotentAspect {
    @Autowired
    private RedisTemplate redisTemplate;


//    @Pointcut("@annotation(com.ruoyi.common.security.annotation.Idempotent)")
//    public void pointCut() {
//
//    }

    @Before("@annotation(idempotent)")
    public void beforePointCut(JoinPoint joinPoint,Idempotent idempotent) {

        Idempotent annotationLog = getAnnotationLog(joinPoint);
        if (annotationLog == null) {
            return;
        }
        String key = parseKey(annotationLog, joinPoint);
        if (StringUtils.isEmpty(key)) {
            return;
        }
        if (redisTemplate.hasKey(key)) {
            log.error(annotationLog.info());
            throw new RuntimeException(annotationLog.info());
        }
        RedisAtomicLong aLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        setExpireTime(annotationLog, aLong);
    }

    @After("@annotation(idempotent)")
    public void afterPointCut(JoinPoint joinPoint,Idempotent idempotent) {
        Idempotent annotationLog = getAnnotationLog(joinPoint);
        if (annotationLog == null) {
            return;
        }
        String key = parseKey(annotationLog, joinPoint);
        if (StringUtils.isEmpty(key)) {
            return;
        }
        if (annotationLog.afterDelete()) {
            redisTemplate.delete(key);
        }

    }


    private void setExpireTime(Idempotent annotationLog, RedisAtomicLong aLong) {
        if (annotationLog.timeUnit().equals(TimeUnit.MILLISECONDS)) {
            aLong.expire(Duration.ofMillis(annotationLog.expireTime()));
        }
        if (annotationLog.timeUnit().equals(TimeUnit.SECONDS)) {
            aLong.expire(Duration.ofSeconds(annotationLog.expireTime()));
        }
        if (annotationLog.timeUnit().equals(TimeUnit.MINUTES)) {
            aLong.expire(Duration.ofMinutes(annotationLog.expireTime()));
        }
        if (annotationLog.timeUnit().equals(TimeUnit.HOURS)) {
            aLong.expire(Duration.ofHours(annotationLog.expireTime()));
        }
    }

    private Idempotent getAnnotationLog(JoinPoint joinPoint) {
        Method method = getMethod(joinPoint);
        if (method != null) {
            return method.getAnnotation(Idempotent.class);
        }
        return null;
    }

    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature.getMethod();
    }

    public String parseKey(Idempotent idempotent, JoinPoint point) {
        String key = "";
        Object[] arguments = point.getArgs();
        if (arguments == null || arguments.length == 0) {
            return null;
        }
        for (Object argument : arguments) {
            Field[] declaredFields = argument.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                field.setAccessible(true);
                try {
                    if (idempotent.key().equals(field.getName())) {
                        key = getMethod(point).getName() + ":" + field.get(argument).toString();
                        return key;
                    }
                } catch (IllegalAccessException e) {
                    log.error("解析失败");
                    throw new RuntimeException("解析失敗");
                }
            }
        }
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String ip = IpUtils.getIpAddr(request);
        key = getMethod(point).getName() + ":" + ip;
        return key;
    }
}
